
Ce tutorial présente une approche de base pour gérer des données dans une application MAUI en utilisant uniquement du code-behind (sans MVVM) et de la sérialisation JSON pour la persistance.
Apprendre à créer une application de gestion de paquets de cartes (decks) avec les opérations CRUD :
Cette application utilise :
Créer un nouveau projet MAUI nommé DeckManager en suivant les étapes du tutorial hello world ou reprendre un projet existant (par exemple, le projet Flashcard...).
Nous allons organiser le projet avec les dossiers suivants :
Créer ces dossiers dans l'explorateur de solution :

Dans le dossier Models, créer une nouvelle classe Deck.cs :
namespace DeckManager.Models
{
public class Deck
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime CreatedDate { get; set; }
public int CardCount { get; set; }
public Deck()
{
CreatedDate = DateTime.Now;
}
public override string ToString()
{
return $"{Name} ({CardCount} cards)";
}
}
}
Le format JSON est simple, lisible et parfait pour stocker des données structurées sans avoir besoin d'une base de données complexe.
Dans le dossier Services, créer la classe JsonDataService.cs :
using System.Text.Json;
using DeckManager.Models;
namespace DeckManager.Services
{
public class JsonDataService
{
private readonly string _filePath;
public JsonDataService()
{
// Path to store the JSON file in app data
_filePath = Path.Combine(
FileSystem.AppDataDirectory,
"decks.json"
);
}
public async Task<List<Deck>> LoadDecksAsync()
{
try
{
if (!File.Exists(_filePath))
{
return new List<Deck>();
}
string json = await File.ReadAllTextAsync(_filePath);
List<Deck>? decks = JsonSerializer.Deserialize<List<Deck>>(json);
return decks ?? new List<Deck>();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error loading: {ex.Message}");
return new List<Deck>();
}
}
public async Task SaveDecksAsync(List<Deck> decks)
{
try
{
JsonSerializerOptions options = new JsonSerializerOptions
{
WriteIndented = true
};
string json = JsonSerializer.Serialize(decks, options);
await File.WriteAllTextAsync(_filePath, json);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error saving: {ex.Message}");
}
}
public string GetFilePath()
{
return _filePath;
}
}
}
Ajoutez une nouvelle page XAML dans le dossier racine : DecksPage.xaml

Remplacez le contenu de DecksPage.xaml :
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DeckManager.DecksPage"
Title="Mes Decks">
<Grid RowDefinitions="Auto,*,Auto" Padding="10">
<!-- Header with add button -->
<HorizontalStackLayout Grid.Row="0"
Spacing="10"
Margin="0,0,0,10">
<Entry x:Name="NewDeckEntry"
Placeholder="Nom du nouveau deck"
HorizontalOptions="FillAndExpand" />
<Button Text="Ajouter"
Clicked="OnAddDeckClicked"
BackgroundColor="#2196F3"
TextColor="White" />
</HorizontalStackLayout>
<!-- Decks list -->
<CollectionView Grid.Row="1"
x:Name="DecksCollectionView"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame Margin="0,5"
Padding="10"
BorderColor="LightGray"
CornerRadius="8"
HasShadow="True">
<Grid ColumnDefinitions="*,Auto,Auto">
<!-- Deck info -->
<StackLayout Grid.Column="0"
VerticalOptions="Center">
<Label Text="{Binding Name}"
FontSize="18"
FontAttributes="Bold" />
<Label Text="{Binding CardCount, StringFormat='{0} cartes'}"
FontSize="14"
TextColor="Gray" />
<Label Text="{Binding CreatedDate, StringFormat='Créé le {0:dd/MM/yyyy}'}"
FontSize="12"
TextColor="LightGray" />
</StackLayout>
<!-- Edit button -->
<Button Grid.Column="1"
Text="✏️"
FontSize="20"
BackgroundColor="Transparent"
Clicked="OnEditDeckClicked"
CommandParameter="{Binding .}" />
<!-- Delete button -->
<Button Grid.Column="2"
Text="🗑️"
FontSize="20"
BackgroundColor="Transparent"
Clicked="OnDeleteDeckClicked"
CommandParameter="{Binding .}" />
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
<!-- Empty view -->
<CollectionView.EmptyView>
<StackLayout HorizontalOptions="Center"
VerticalOptions="Center">
<Label Text="📚"
FontSize="60"
HorizontalOptions="Center" />
<Label Text="Aucun deck"
FontSize="18"
TextColor="Gray"
HorizontalOptions="Center" />
<Label Text="Créez votre premier deck ci-dessus"
FontSize="14"
TextColor="LightGray"
HorizontalOptions="Center" />
</StackLayout>
</CollectionView.EmptyView>
</CollectionView>
<!-- Info bar -->
<Label Grid.Row="2"
x:Name="InfoLabel"
Text="Prêt"
FontSize="12"
TextColor="Gray"
HorizontalOptions="Center"
Margin="0,10,0,0" />
</Grid>
</ContentPage>
Ouvrir DecksPage.xaml.cs et remplacer/adapter son contenu comme suit :
using DeckManager.Models;
using DeckManager.Services;
namespace DeckManager
{
public partial class DecksPage : ContentPage
{
private JsonDataService _dataService;
private List<Deck> _decks;
private int _nextId = 1;
public DecksPage()
{
InitializeComponent();
_dataService = new JsonDataService();
_decks = new List<Deck>();
LoadDecks();
}
private async void LoadDecks()
{
_decks = await _dataService.LoadDecksAsync();
// Calculate next ID
if (_decks.Any())
{
_nextId = _decks.Max(d => d.Id) + 1;
}
RefreshView();
UpdateInfo($"Chargé: {_decks.Count} deck(s)");
}
private void RefreshView()
{
DecksCollectionView.ItemsSource = null;
DecksCollectionView.ItemsSource = _decks;
}
private void UpdateInfo(string message)
{
InfoLabel.Text = $"{DateTime.Now:HH:mm:ss} - {message}";
}
}
}
Ajouter la méthode pour créer un nouveau deck :
private async void OnAddDeckClicked(object sender, EventArgs e)
{
string? name = NewDeckEntry.Text?.Trim();
if (string.IsNullOrEmpty(name))
{
await DisplayAlert("Erreur", "Veuillez entrer un nom", "OK");
return;
}
// Create new deck
Deck newDeck = new Deck
{
Id = _nextId++,
Name = name,
CardCount = 0
};
_decks.Add(newDeck);
await _dataService.SaveDecksAsync(_decks);
RefreshView();
NewDeckEntry.Text = string.Empty;
UpdateInfo($"Ajouté: {name}");
}
_nextId pour chaque nouveau deckAjoutez la méthode dans DecksPage.xaml.cs :
private async void OnEditDeckClicked(object sender, EventArgs e)
{
Button? button = sender as Button;
Deck? deck = button?.CommandParameter as Deck;
if (deck == null) return;
// Prompt for new name
string? newName = await DisplayPromptAsync(
"Renommer",
"Nouveau nom du deck:",
initialValue: deck.Name,
placeholder: "Nom du deck"
);
if (string.IsNullOrWhiteSpace(newName))
{
return; // User cancelled or entered empty
}
// Update deck name
deck.Name = newName.Trim();
await _dataService.SaveDecksAsync(_decks);
RefreshView();
UpdateInfo($"Renommé: {newName}");
}
Au lieu d'utiliser un popup, on peut permettre l'édition directement dans la liste avec un Entry qui apparaît au clic.
Une option serait d'avoir un emplacement qu'on affiche/masque pour la partie saisie d'information.
Ceci correspondrait à remplacer le DataTemplate dans DecksPage.xaml comme suit :
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame Margin="0,5"
Padding="10"
BorderColor="LightGray"
CornerRadius="8"
HasShadow="True">
<Grid ColumnDefinitions="*,Auto,Auto,Auto">
<!-- Deck info (normal mode) -->
<StackLayout Grid.Column="0"
VerticalOptions="Center"
x:Name="DisplayStack">
<Label Text="{Binding Name}"
FontSize="18"
FontAttributes="Bold" />
<Label Text="{Binding CardCount, StringFormat='{0} cartes'}"
FontSize="14"
TextColor="Gray" />
<Label Text="{Binding CreatedDate, StringFormat='Créé le {0:dd/MM/yyyy}'}"
FontSize="12"
TextColor="LightGray" />
</StackLayout>
<!-- Edit mode (hidden by default) -->
<HorizontalStackLayout Grid.Column="0"
VerticalOptions="Center"
IsVisible="False"
x:Name="EditStack">
<Entry Text="{Binding Name}"
x:Name="EditEntry"
HorizontalOptions="FillAndExpand"
WidthRequest="200" />
<Button Text="✓"
FontSize="18"
BackgroundColor="Green"
TextColor="White"
Clicked="OnSaveEditClicked"
CommandParameter="{Binding .}" />
<Button Text="✗"
FontSize="18"
BackgroundColor="Red"
TextColor="White"
Clicked="OnCancelEditClicked"
CommandParameter="{Binding .}" />
</HorizontalStackLayout>
<!-- Edit button -->
<Button Grid.Column="1"
Text="✏️"
FontSize="20"
BackgroundColor="Transparent"
Clicked="OnEditDeckInlineClicked"
CommandParameter="{Binding .}"
x:Name="EditButton" />
<!-- Delete button -->
<Button Grid.Column="2"
Text="🗑️"
FontSize="20"
BackgroundColor="Transparent"
Clicked="OnDeleteDeckClicked"
CommandParameter="{Binding .}" />
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
Voici donc une nouvelle page dédiée à la création :
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DeckManager.EditDeckPage"
Title="Modifier le deck">
<StackLayout Padding="20" Spacing="15">
<Label Text="Nom du deck"
FontSize="16"
FontAttributes="Bold" />
<Entry x:Name="NameEntry"
Placeholder="Nom du deck"
FontSize="18" />
<Label Text="Nombre de cartes"
FontSize="16"
FontAttributes="Bold"
Margin="0,20,0,0" />
<HorizontalStackLayout Spacing="10">
<Button Text="-"
Clicked="OnDecrementClicked"
WidthRequest="50"
BackgroundColor="LightGray" />
<Label x:Name="CardCountLabel"
Text="0"
FontSize="24"
VerticalOptions="Center"
HorizontalOptions="Center"
WidthRequest="60"
HorizontalTextAlignment="Center" />
<Button Text="+"
Clicked="OnIncrementClicked"
WidthRequest="50"
BackgroundColor="LightGray" />
</HorizontalStackLayout>
<Button Text="Sauvegarder"
Clicked="OnSaveClicked"
BackgroundColor="#4CAF50"
TextColor="White"
Margin="0,30,0,0" />
<Button Text="Annuler"
Clicked="OnCancelClicked"
BackgroundColor="Gray"
TextColor="White" />
</StackLayout>
</ContentPage>
Et voici la partie de traitement :
using DeckManager.Models;
using DeckManager.Services;
namespace DeckManager
{
public partial class EditDeckPage : ContentPage, IQueryAttributable
{
private Deck _deck;
private int _cardCount;
private JsonDataService _dataService;
private List<Deck> _decks;
public EditDeckPage()
{
InitializeComponent();
}
// Receive navigation parameters
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.TryGetValue("deck", out object? deckObj) && deckObj is Deck deck)
{
_deck = deck;
_cardCount = deck.CardCount;
// Initialize fields
NameEntry.Text = deck.Name;
CardCountLabel.Text = _cardCount.ToString();
}
if (query.TryGetValue("dataService", out object? serviceObj) && serviceObj is JsonDataService service)
{
_dataService = service;
}
if (query.TryGetValue("decks", out object? decksObj) && decksObj is List<Deck> decks)
{
_decks = decks;
}
}
private void OnIncrementClicked(object sender, EventArgs e)
{
_cardCount++;
CardCountLabel.Text = _cardCount.ToString();
}
private void OnDecrementClicked(object sender, EventArgs e)
{
if (_cardCount > 0)
{
_cardCount--;
CardCountLabel.Text = _cardCount.ToString();
}
}
private async void OnSaveClicked(object sender, EventArgs e)
{
string? newName = NameEntry.Text?.Trim();
if (string.IsNullOrWhiteSpace(newName))
{
await DisplayAlert("Erreur", "Le nom ne peut pas être vide", "OK");
return;
}
// Update deck
_deck.Name = newName;
_deck.CardCount = _cardCount;
// Save immediately to JSON
await _dataService.SaveDecksAsync(_decks);
await Shell.Current.GoToAsync("..");
}
private async void OnCancelClicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("..");
}
}
}
La ligne suivante peut sembler complexe :
if (query.TryGetValue("deck", out var deckObj) && deckObj is Deck deck)
Décomposons-la étape par étape pour comprendre ce qui se passe :
// Étape 1 : Essayer de récupérer la valeur avec la clé "deck"
bool found = query.TryGetValue("deck", out object? deckObj);
// Étape 2 : Vérifier que la valeur a été trouvée
if (found)
{
// Étape 3 : Vérifier que l'objet est bien de type Deck
if (deckObj is Deck)
{
// Étape 4 : Convertir (cast) vers le type Deck
Deck deck = (Deck)deckObj;
// Étape 5 : Utiliser la variable deck
_deck = deck;
// ...
}
}
La syntaxe condensée combine toutes ces étapes :
if (query.TryGetValue("deck", out object? deckObj) && deckObj is Deck deck)
{
_deck = deck;
// ...
}
query.TryGetValue("deck", out var deckObj)"deck" dans le dictionnairetrue ET stocke la valeur dans deckObjfalse&& (ET logique)trueTryGetValue retourne falsedeckObj is Deck deckdeckObj est de type Decktrue ET crée une variable deck typéefalseif s'exécute SEULEMENT si :deck est directement utilisable (typée)Il faut maintenant adapter la page initiale en remplaçant la méthode OnEditDeckClicked dans DecksPage.xaml.cs.
Pour rappel, il existe plusieurs manières de passer des paramètres entre les pages avec le ‘shell' et nous suggérons d'utiliser
la version la plus polyvalente, même si elle demande un peu plus de code :
private async void OnEditDeckClicked(object sender, EventArgs e)
{
Button? button = sender as Button;
Deck? deck = button?.CommandParameter as Deck;
if (deck == null) return;
// Navigate to edit page using Shell
// Pass deck, dataService and decks list so EditDeckPage can save
Dictionary<string, object> navigationParameter = new Dictionary<string, object>
{
{ "deck", deck },
{ "dataService", _dataService },
{ "decks", _decks }
};
await Shell.Current.GoToAsync("EditDeck", navigationParameter);
}
// Refresh view when returning from edit page
protected override void OnAppearing()
{
base.OnAppearing();
// Just refresh the display (save is done in EditDeckPage)
RefreshView();
}
En passant _dataService et _decks à la page d'édition, celle-ci peut sauvegarder immédiatement lorsque l'utilisateur clique sur "Sauvegarder". C'est plus clair et intuitif :
Une alternative consiste à utiliser un registre de services via une classe statique, par exemple, ou en utilisant le système .NET un peu plus complexe
Ajoutez la méthode dans DecksPage.xaml.cs :
private async void OnDeleteDeckClicked(object sender, EventArgs e)
{
Button? button = sender as Button;
Deck? deck = button?.CommandParameter as Deck;
if (deck == null) return;
// Confirm deletion
bool confirm = await DisplayAlert(
"Confirmation",
$"Voulez-vous vraiment supprimer '{deck.Name}' ?",
"Supprimer",
"Annuler"
);
if (!confirm) return;
// Remove deck
_decks.Remove(deck);
await _dataService.SaveDecksAsync(_decks);
RefreshView();
UpdateInfo($"Supprimé: {deck.Name}");
}
confirm indique le choix de l'utilisateurDans ce tutorial, nous utilisons une méthode RefreshView() qui réassigne la liste :
private void RefreshView()
{
DecksCollectionView.ItemsSource = null;
DecksCollectionView.ItemsSource = _decks;
}
Cette approche simple force le CollectionView à se rafraîchir complètement. Mais pourquoi est-ce nécessaire ?
Le problème : Lorsqu'on utilise une List<Deck>, le CollectionView ne détecte PAS automatiquement :
_decks.Add(...))_decks.Remove(...))deck.Name = "nouveau nom")ObservableCollection<T> est une collection intelligente qui notifie automatiquement les changements :
INotifyPropertyChanged sur la classe Deck)Voici les changements à apporter pour utiliser ObservableCollection :
using System.Collections.ObjectModel; // Ajouter ce using
using DeckManager.Models;
using DeckManager.Services;
namespace DeckManager
{
public partial class DecksPage : ContentPage
{
private JsonDataService _dataService;
private ObservableCollection<Deck> _decks; // List devient ObservableCollection
private int _nextId = 1;
public DecksPage()
{
InitializeComponent();
_dataService = new JsonDataService();
_decks = new ObservableCollection<Deck>(); // new ObservableCollection
LoadDecks();
}
private async void LoadDecks()
{
List<Deck> loadedDecks = await _dataService.LoadDecksAsync();
// Clear and repopulate ObservableCollection
_decks.Clear();
foreach (Deck deck in loadedDecks)
{
_decks.Add(deck);
}
if (_decks.Any())
{
_nextId = _decks.Max(d => d.Id) + 1;
}
// Assign ItemsSource ONCE (no need to reassign every time)
if (DecksCollectionView.ItemsSource == null)
{
DecksCollectionView.ItemsSource = _decks;
}
UpdateInfo($"Chargé: {_decks.Count} deck(s)");
}
Avec ObservableCollection, plus besoin de rafraîchir manuellement après un ajout ou une suppression :
Méthode OnAddDeckClicked :
private async void OnAddDeckClicked(object sender, EventArgs e)
{
string? name = NewDeckEntry.Text?.Trim();
if (string.IsNullOrEmpty(name))
{
await DisplayAlert("Erreur", "Veuillez entrer un nom", "OK");
return;
}
Deck newDeck = new Deck
{
Id = _nextId++,
Name = name,
CardCount = 0
};
_decks.Add(newDeck); // ← La vue se met à jour automatiquement !
await _dataService.SaveDecksAsync(_decks.ToList());
// RefreshView(); ← SUPPRIMÉ !
NewDeckEntry.Text = string.Empty;
UpdateInfo($"Ajouté: {name}");
}
Méthode OnDeleteDeckClicked :
private async void OnDeleteDeckClicked(object sender, EventArgs e)
{
Button? button = sender as Button;
Deck? deck = button?.CommandParameter as Deck;
if (deck == null) return;
bool confirm = await DisplayAlert(
"Confirmation",
$"Voulez-vous vraiment supprimer '{deck.Name}' ?",
"Supprimer",
"Annuler"
);
if (!confirm) return;
_decks.Remove(deck); // ← La vue se met à jour automatiquement !
await _dataService.SaveDecksAsync(_decks.ToList());
// RefreshView(); ← SUPPRIMÉ !
UpdateInfo($"Supprimé: {deck.Name}");
}
Méthode OnAppearing :
protected override void OnAppearing()
{
base.OnAppearing();
// Nécessaire pour refléter les modifications de propriétés (ex: deck.Name changé dans EditDeckPage)
// ObservableCollection détecte les ajouts/suppressions, mais PAS les changements de propriétés
RefreshView();
}
La méthode SaveDecksAsync attend une List<Deck>, donc convertir :
await _dataService.SaveDecksAsync(_decks.ToList());
Ou modifier JsonDataService pour accepter IEnumerable<Deck> :
public async Task SaveDecksAsync(IEnumerable<Deck> decks)
{
try
{
var options = new JsonSerializerOptions
{
WriteIndented = true
};
var json = JsonSerializer.Serialize(decks, options);
await File.WriteAllTextAsync(_filePath, json);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error saving: {ex.Message}");
}
}
private ObservableCollection<Deck> _decks; // ← Change type
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
// ...
if (query.TryGetValue("decks", out object? decksObj) && decksObj is ObservableCollection<Deck> decks)
{
_decks = decks;
}
}
Et dans OnSaveClicked :
// Update deck (ObservableCollection détecte le changement si on remplace l'objet)
_deck.Name = newName;
_deck.CardCount = _cardCount;
// Save to JSON
await _dataService.SaveDecksAsync(_decks.ToList());
Aspect | List + RefreshView | ObservableCollection |
Simplicité | Très simple | Un peu plus de code |
Performance | Rafraîchit TOUT | Met à jour uniquement l'élément modifié |
Détection automatique | Non | Oui (ajout/suppression) |
Code explicite | Clair où ça rafraîchit | "Magique" |
Propriétés modifiées | Non détecté | Non détecté (besoin de INotifyPropertyChanged) |
List + RefreshView :
ObservableCollection :
Si besoin, voici comment configurer AppShell.xaml pour le tutoriel :
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="DeckManager.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DeckManager">
<ShellContent
Title="Decks"
ContentTemplate="{DataTemplate local:DecksPage}"
Route="DecksPage" />
</Shell>
Dans App.xaml.cs, enregistrez la route pour EditDeckPage :
namespace DeckManager
{
public partial class App : Application
{
public App()
{
InitializeComponent();
// Register navigation routes
Routing.RegisterRoute("EditDeck", typeof(EditDeckPage));
MainPage = new AppShell();
}
}
}
Shell.Current.GoToAsync("RouteName")Il est temps de faire un tour des fonctionnalités pour vérifier que tout est en ordre.
Ajouter / Adapter ces fonctionnalités pour améliorer l'expérience utilisateur :
// Sort decks by name
private void SortDecks()
{
_decks = _decks.OrderBy(d => d.Name).ToList();
RefreshView();
}
// Search filter
private void OnSearchTextChanged(object sender, TextChangedEventArgs e)
{
string searchText = e.NewTextValue?.ToLower() ?? "";
if (string.IsNullOrWhiteSpace(searchText))
{
DecksCollectionView.ItemsSource = _decks;
}
else
{
List<Deck> filtered = _decks.Where(d =>
d.Name.ToLower().Contains(searchText)
).ToList();
DecksCollectionView.ItemsSource = filtered;
}
}
<SearchBar Placeholder="Rechercher un deck..."
TextChanged="OnSearchTextChanged"
Margin="0,0,0,10" />
Ajouter une propriété Color au modèle Deck et assigner des couleurs :
public class Deck
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime CreatedDate { get; set; }
public int CardCount { get; set; }
public string Color { get; set; } = "#2196F3";
// ...
}
Ajouter un bouton de debug dans une nouvelle page "debug" :
private async void OnShowPathClicked(object sender, EventArgs e)
{
string path = _dataService.GetFilePath();
await DisplayAlert("Fichier JSON", path, "OK");
}
Voici à quoi ressemble le fichier decks.json :
[
{
"Id": 1,
"Name": "Anglais - Vocabulaire",
"CreatedDate": "2024-01-15T10:30:00",
"CardCount": 25
},
{
"Id": 2,
"Name": "Mathématiques",
"CreatedDate": "2024-01-15T11:00:00",
"CardCount": 15
},
{
"Id": 3,
"Name": "Histoire - Dates importantes",
"CreatedDate": "2024-01-15T14:20:00",
"CardCount": 40
}
]
using DeckManager.Models;
using DeckManager.Services;
namespace DeckManager
{
public partial class DecksPage : ContentPage
{
private JsonDataService _dataService;
private List<Deck> _decks;
private int _nextId = 1;
public DecksPage()
{
InitializeComponent();
_dataService = new JsonDataService();
_decks = new List<Deck>();
LoadDecks();
}
private async void LoadDecks()
{
_decks = await _dataService.LoadDecksAsync();
if (_decks.Any())
{
_nextId = _decks.Max(d => d.Id) + 1;
}
RefreshView();
UpdateInfo($"Chargé: {_decks.Count} deck(s)");
}
private void RefreshView()
{
DecksCollectionView.ItemsSource = null;
DecksCollectionView.ItemsSource = _decks;
}
private void UpdateInfo(string message)
{
InfoLabel.Text = $"{DateTime.Now:HH:mm:ss} - {message}";
}
private async void OnAddDeckClicked(object sender, EventArgs e)
{
string? name = NewDeckEntry.Text?.Trim();
if (string.IsNullOrEmpty(name))
{
await DisplayAlert("Erreur", "Veuillez entrer un nom", "OK");
return;
}
Deck newDeck = new Deck
{
Id = _nextId++,
Name = name,
CardCount = 0
};
_decks.Add(newDeck);
await _dataService.SaveDecksAsync(_decks);
RefreshView();
NewDeckEntry.Text = string.Empty;
UpdateInfo($"Ajouté: {name}");
}
private async void OnEditDeckClicked(object sender, EventArgs e)
{
Button? button = sender as Button;
Deck? deck = button?.CommandParameter as Deck;
if (deck == null) return;
// Navigate to edit page using Shell
// Pass deck, dataService and decks list so EditDeckPage can save
Dictionary<string, object> navigationParameter = new Dictionary<string, object>
{
{ "deck", deck },
{ "dataService", _dataService },
{ "decks", _decks }
};
await Shell.Current.GoToAsync("EditDeck", navigationParameter);
}
protected override void OnAppearing()
{
base.OnAppearing();
// Just refresh the display (save is done in EditDeckPage)
RefreshView();
}
private async void OnDeleteDeckClicked(object sender, EventArgs e)
{
Button? button = sender as Button;
Deck? deck = button?.CommandParameter as Deck;
if (deck == null) return;
bool confirm = await DisplayAlert(
"Confirmation",
$"Voulez-vous vraiment supprimer '{deck.Name}' ?",
"Supprimer",
"Annuler"
);
if (!confirm) return;
_decks.Remove(deck);
await _dataService.SaveDecksAsync(_decks);
RefreshView();
UpdateInfo($"Supprimé: {deck.Name}");
}
}
}
Suite à ce tutorial, les éléments suivants ont été pratiqué :
Approche | Avantages | Inconvénients |
DisplayPrompt | Simple, rapide à implémenter | Limité à une seule propriété |
Page dédiée | Interface complète, plusieurs propriétés | Un peu plus de code |
Cette base est parfaite pour la suite du projet Flashcard, soit:
Voici un quizz pour vérifier les aspects clé des éléments présentés